import { defineComponent, reactive, onUnmounted, computed, createVNode } from 'vue';
import DraggableCore from './DraggableCore.js';
import { createCSSTransform, createDraggableData, getBoundPosition, canDragX, canDragY } from './utils.js';

const Draggable = /* @__PURE__ */ defineComponent({
  name: 'Draggable',
  props: {
    defaultPosition: {
      type: Object
    },
    position: {
      type: Object
    },
    scale: {
      type: Number
    },
    bounds: {
      type: String
    },
    axis: {
      type: String
    },
    positionOffset: {
      type: Object
    },
    grid: {
      type: Array
    },
    disabled: {
      type: Boolean
    },
    handle: {
      type: [String, Object]
    },
    onStart: {
      type: Function
    },
    onDrag: {
      type: Function
    },
    onStop: {
      type: Function
    }
  },
  setup(props, {
    expose,
    slots
  }) {
    const defaultPosition = props.defaultPosition || {
      x: 0,
      y: 0
    };
    const store = reactive({
      // Whether or not we are currently dragging.
      dragging: false,
      // Whether or not we have been dragged before.
      dragged: false,
      // Current transform x and y.
      x: props.position ? props.position.x : defaultPosition.x,
      y: props.position ? props.position.y : defaultPosition.y,
      prevPropsPosition: {
        ...props.position
      },
      // Used for compensating for out-of-bounds drags
      slackX: 0,
      slackY: 0
    });
    const scale = props.scale || 1;
    const bounds = props.bounds || false;
    const onDragStart = (e, coreData) => {
      // Short-circuit if user's callback killed it.
      const shouldStart = props.onStart && props.onStart(e, createDraggableData(store, scale, coreData));
      // Kills start event on core as well, so move handlers are never bound.
      if (shouldStart === false) return false;
      store.dragging = true;
      store.dragged = true;
    };
    const onDrag = (e, coreData) => {
      if (!store.dragging) return false;
      const uiData = createDraggableData(store, scale, coreData);
      const newState = {
        x: uiData.x,
        y: uiData.y,
        slackX: 0,
        slackY: 0
      };

      // Keep within bounds.
      if (bounds) {
        // Save original x and y.
        const {
          x,
          y
        } = newState;

        // Add slack to the values used to calculate bound position. This will ensure that if
        // we start removing slack, the element won't react to it right away until it's been
        // completely removed.
        newState.x += store.slackX;
        newState.y += store.slackY;

        // Get bound position. This will ceil/floor the x and y within the boundaries.
        const [newStateX, newStateY] = getBoundPosition({
          bounds,
          node: coreData.node
        }, newState.x, newState.y);
        newState.x = newStateX;
        newState.y = newStateY;

        // Recalculate slack by noting how much was shaved by the boundPosition handler.
        newState.slackX = store.slackX + (x - newState.x);
        newState.slackY = store.slackY + (y - newState.y);

        // Update the event we fire to reflect what really happened after bounds took effect.
        uiData.x = newState.x;
        uiData.y = newState.y;
        uiData.deltaX = newState.x - store.x;
        uiData.deltaY = newState.y - store.y;
      }

      // Short-circuit if user's callback killed it.
      const shouldUpdate = props.onDrag && props.onDrag(e, uiData);
      if (shouldUpdate === false) return false;
      store.x = newState.x;
      store.y = newState.y;
      store.slackX = newState.slackX;
      store.slackY = newState.slackY;
    };
    const onDragStop = (e, coreData) => {
      if (!store.dragging) return false;

      // Short-circuit if user's callback killed it.
      const shouldContinue = props.onStop && props.onStop(e, createDraggableData(store, scale, coreData));
      if (shouldContinue === false) return false;
      store.dragging = false;
      store.slackX = 0;
      store.slackY = 0;
    };
    onUnmounted(() => {
      store.dragging = false;
    });
    const axis = props.axis || 'both';
    const style = computed(() => {
      const x = store.x;
      const y = store.y;
      const transformOpts = () => ({
        // Set left if horizontal drag is enabled
        x: canDragX(axis) ? x : defaultPosition.x,
        // Set top if vertical drag is enabled
        y: canDragY(axis) ? y : defaultPosition.y
      });
      return {
        ...createCSSTransform(transformOpts(), props.positionOffset)
      };
    });
    const classList = computed(() => ({
      'cm-draggable': true,
      'cm-draggable-dragging': store.dragging,
      'cm-draggable-dragged': store.dragged
    }));

    // 暴露接口
    expose({
      reset: () => {
        store.x = 0;
        store.y = 0;
      },
      setPosition(pos) {
        store.x = pos.x;
        store.y = pos.y;
      }
    });
    return () => createVNode(DraggableCore, {
      "grid": props.grid,
      "class": classList.value,
      "disabled": props.disabled,
      "handle": props.handle,
      "scale": scale,
      "style": style.value,
      "onStart": onDragStart,
      "onDrag": onDrag,
      "onStop": onDragStop,
      "ref": el => el
    }, {
      default: () => [slots.default?.()]
    });
  }
});

export { Draggable as default };
